#include "mesh.h"

#include <fstream>
#include <limits>

namespace Sirat {

namespace Object {

struct Face {
	Face() { }
	~Face() { }
	Math::Vector a,b,c,d;
};
	
Mesh::Mesh(Material::Object_Material* _object_material,const std::string& _filename,const unsigned short& _type) : Object(_object_material) , filename(_filename), type(_type), loaded(true) {
	load();
}

Mesh::Mesh() : Object(), loaded(false) { }

Mesh::Mesh(const Mesh& _mesh) : Object(_mesh) , loaded(true) {
	type = _mesh.type;
	filename = _mesh.filename;
	
	load();
}

Mesh::~Mesh() { }

Math::Vector Mesh::get_normal(const Math::Vector& _point) const {
	return Math::Vector(0,0,0);
}

tuple<double,Math::Vector,Object*> Mesh::intersection(const Math::Ray& _ray) {
	if(!loaded)
		load();
	// Same algorithm from scene::nearest_intersection
	std::numeric_limits<double> double_limit;
	const double infinity = double_limit.infinity();
	double distance = infinity;
	Object* nearest_object = 0;
	int j = -1;
	Math::Vector point;
	for(unsigned int i =0;i<mesh.size();i++) {
		tuple<double,Math::Vector,Object*> test_intersection = mesh.at(i)->intersection(_ray);
		double test_distance = test_intersection.get<0>();
		if(test_distance < distance) { /* Searching the nearest */
			point = test_intersection.get<1>();
			j = i;
			distance = test_distance;
		}
	}
	if(j != -1) { 
		nearest_object = mesh.at(j);
	}
	return make_tuple(distance,point,nearest_object);
}

void Mesh::scale(const double _scale_factor) {
	
	for(unsigned int i =0;i<mesh.size();i++) {
		if(type == 3) {
			Triangle* triangle = static_cast<Triangle*> (mesh.at(i));
			for(short unsigned int j = 0; j < type ; j++) {
				Math::Vector vertex = triangle->get_vertex(j);
				triangle->set_vertex(vertex*_scale_factor,j);
			}
			
		} else if(type == 4) {
			Quadrilateral* quadrilateral  = static_cast<Quadrilateral*> (mesh.at(i));
			for(short unsigned int j = 0; j < type ; j++) {
				Math::Vector vertex = quadrilateral->get_vertex(j);
				quadrilateral->set_vertex(vertex*_scale_factor,j);
			}
		}
	}
}

void Mesh::translate(const Math::Vector& _translation_vector) {
	for(unsigned int i =0;i<mesh.size();i++) {
		if(type == 3) {
			Triangle* triangle = static_cast<Triangle*> (mesh.at(i));
			for(short unsigned int j = 0; j < type ; j++) {
				Math::Vector vertex = triangle->get_vertex(j);
				triangle->set_vertex(vertex+_translation_vector,j);
			}
			
		} else if(type == 4) {
			Quadrilateral* quadrilateral  = static_cast<Quadrilateral*> (mesh.at(i));
			for(short unsigned int j = 0; j < type ; j++) {
				Math::Vector vertex = quadrilateral->get_vertex(j);
				quadrilateral->set_vertex(vertex+_translation_vector,j);
			}
		}
	}
}

Mesh& Mesh::operator=(const Mesh& _mesh) {
	Object::operator=(_mesh);
	type = _mesh.type;
	filename = _mesh.filename;
	return *this;
}

void Mesh::load() {
	if(type == 3)
		load_triangle(filename);
	else if(type == 4)
		load_quadrilateral(filename);
	
	loaded = true;
}

void Mesh::load_quadrilateral(const std::string& _filename) {
	std::vector<Math::Vector> vertices;
	std::vector<Face> faces;
	char type;
	int f1,f2,f3,f4;
	double v1,v2,v3;

	std::ifstream fin;
	fin.open(_filename.c_str());
	if(!fin) return;
	while(fin >> type) {
		//Vertex
		if(type == 'v') {	
			fin >> v1  >> v2 >> v3;
			Math::Vector v(v1,v2,v3);
			vertices.push_back(v);

		} 
		// Face
		else if(type == 'f') {
			fin >> f1 >> f2 >> f3 >> f4;
			Face f; 
			f.a = vertices.at(f1-1);
			f.b = vertices.at(f2-1);
			f.c = vertices.at(f3-1);
			f.d = vertices.at(f4-1);
			faces.push_back(f);
		}
	}
	for(unsigned int i = 0; i < faces.size(); i++) {
		Object* face = new Quadrilateral(object_material,faces[i].a,faces[i].b,faces[i].c,faces[i].d);
		mesh.push_back(face);
	}
	fin.close();
}
void Mesh::load_triangle(const std::string& _filename) {
	std::vector<Math::Vector> vertices;
	std::vector<Face> faces;
	char type;
	int f1,f2,f3;
	double v1,v2,v3;
	
	std::ifstream fin;
	fin.open(_filename.c_str());
	if(!fin) return;
	while(fin >> type) {
		//Vertex
		if(type == 'v') {	
			fin >> v1  >> v2 >> v3;
			Math::Vector v(v1,v2,v3);
			vertices.push_back(v);

		} 
		// Face
		else if(type == 'f') {
			fin >> f1 >> f2 >> f3;
			Face f; 
			f.a = vertices.at(f1-1);
			f.b = vertices.at(f2-1);
			f.c = vertices.at(f3-1);
			faces.push_back(f);
		}
	}
	for(unsigned int i = 0; i < faces.size(); i++) {
		Object* face = new Triangle(object_material,faces[i].a,faces[i].b,faces[i].c);
		mesh.push_back(face);
	}
	fin.close();
}

}

}
